Skip to content

test(driver-sql): 把 pagination / filter-logic 两条共享矩阵也跑到 driver 轴上 (#4714) - #4735

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4714-driver-axis-pagination-filter
Aug 3, 2026
Merged

test(driver-sql): 把 pagination / filter-logic 两条共享矩阵也跑到 driver 轴上 (#4714)#4735
os-zhuang merged 2 commits into
mainfrom
claude/issue-4714-driver-axis-pagination-filter

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4714

#4245(PR #4713)把 D-A3 的 driver 轴补到了 temporal 矩阵,并留下 live-dialect-matrix.testkit.ts。本 PR 照抄那个形状,把 driver-sql 还在消费的另外两条 @objectstack/spec/data 共享矩阵也搬上同一根轴:

文件 共享矩阵 之前 现在
sql-driver-pagination-conformance.test.ts PAGINATION_CASES / PAGINATION_UNORDERED_CASES 两个 describe 都钉死 client: 'better-sqlite3' for (const cell of DIALECT_CELLS)
sql-driver-or-filter.test.ts FILTER_LOGIC_CASES 同上,describe 名字就叫 (SQLite) 同上

结论先说:格子存在,而且真的会咬人 —— 但当前实现是绿的

按 issue 的验收,「红了是交付」。这一轮的实测结果是:三个方言 46 条用例逐格一致,全绿。这不是因为断言被放松了,而是因为 paginationTieBreaker 的修复在真服务器上确实成立。为了证明这一格不是空转,我做了一次破坏性实验(临时把 paginationTieBreaker 改成对 managed 表也返回 null,测完已 git checkout 还原,未进入本 PR 的 diff):

格子 破坏后失败数 失败的是哪一半
sqlite 8 性质断言的有序用例一条都没红;红的只有 3 条 unordered walk + 5 条 emitted-SQL
live postgres 15 性质断言红了 7 条 —— visits every row exactly onceexpected 11 to be 12(一行被翻了两次、另一行一次都没出现),orders pages consistently 红 4 条
live mysql 5 只有 emitted-SQL 那一半(InnoDB 主键序全表扫描在 12 行上恰好稳定)

PG 上那句 expected 11 to be 12 就是 objectui#3106 的原始故障,一字不差 —— 这正是文件头注自己说 SQLite 上证不出来的那半边。换句话说:在补上这根轴之前,删掉分页修复只会让 emitted-SQL 那半边红;性质断言在唯一跑得到的方言上是恒真的。

破坏实验的 PG 证据:

FAIL ... (live postgres) > visits every row exactly once — single ascending key with 4-row ties
AssertionError: expected 11 to be 12 // Object.is equality
FAIL ... (live postgres) > orders pages consistently with the requested sort — single ascending key with 4-row ties
- Expected      + Received
-   "r09",          "r02",
    "r02",          "r10",
    "r10",          "r03",
    "r03",      +   "r09",
-   "r06",      +   "r12",

r06 整趟走丢,r12 出现两次。)

实测环境与逐格证据

本地起了真服务器,配置与 CI 的 Temporal Conformance (live PG + MySQL) job 对齐:PostgreSQL 16.13 @ Asia/Shanghai、MariaDB 10.11.14 @ +08:00,进程 TZ=America/New_York,并开 OS_EXPECT_LIVE_DIALECT_MATRIX=1

$ TZ=America/New_York OS_TEST_POSTGRES_URL=... OS_TEST_MYSQL_URL=... \
  OS_EXPECT_LIVE_DIALECT_MATRIX=1 vitest run \
  src/sql-driver-pagination-conformance.test.ts src/sql-driver-or-filter.test.ts

sqlite         passed: 46
live postgres  passed: 46
live mysql     passed: 46
 Test Files  2 passed (2)
      Tests  138 passed (138)

三格用例数完全相同(46 × 3 = 138),行 id 集合逐格一致。整包(CI 形状,含 temporal 矩阵与全部 live 套件):

$ pnpm --filter @objectstack/driver-sql test        # 无 URL
 Test Files  55 passed | 4 skipped (59)
      Tests  636 passed | 44 skipped (680)

$ TZ=... OS_TEST_*_URL=... OS_EXPECT_LIVE_DIALECT_MATRIX=1 pnpm --filter @objectstack/driver-sql test
 Test Files  59 passed (59)
      Tests  844 passed (844)

说明:MariaDB 10.11 不等于 CI 的 MySQL 8.0(容器里没有 docker daemon,只能用系统自带的 server)。仓库注释里说 #3942 的手工验证正是跑在 MariaDB 10.11 上,两者互为补充;MySQL 8.0 那一格由 CI 上这个 job 给出。

非空转守卫(没有退化)

缺 URL → 具名 skip,不是静默跳过:

↓ ... > sql-driver — paged-read conformance matrix (live postgres) > is provisioned — set OS_TEST_POSTGRES_URL to run this cell of the D-A3 driver axis
↓ ... > sql-driver — filter-logic conformance matrix (live mysql) > is provisioned — set OS_TEST_MYSQL_URL to run this cell of the D-A3 driver axis

OS_EXPECT_LIVE_DIALECT_MATRIX=1 且缺 URL → 直接红(实测 4 条):

FAIL ... > sql-driver — paged-read conformance matrix (live postgres) > is provisioned — ...
AssertionError: OS_TEST_POSTGRES_URL is unset while OS_EXPECT_LIVE_DIALECT_MATRIX=1: this runner
declared it provisions live Postgres and MySQL, so the live postgres cell of the paged-read
conformance matrix must not be skipped (ADR-0053 D-A3 "Postgres at minimum").

这个守卫从 temporal 文件里上提到了 testkitdeclareUnprovisionedCell(cell, matrix)),三条矩阵共用一份定义 —— 复制三份的守卫可以在其中一份里悄悄变弱,而「巡检发现零个 cell 却报 OK」正是 #4646 已经付过一次学费的形状。temporal 文件的调用点同步改掉,行为与文案逐字不变。

几处刻意的判断(请重点看这三条)

  1. 不加 server-timezone 轴。 D-B3 的三方错开守卫属于 temporal 矩阵 —— 那里的答案会被时区搬动。分页和 $or 里没有任何一个 instant 参与比较,硬要求非 UTC 服务器只会制造出与本矩阵无关的红。
  2. unpaged + 无 orderBy 的那条,精确顺序只钉 SQLite。 分页读取在没有 orderBy 时同样不确定:tie-breaker 只覆盖了「排了序的翻页」 #4363 承诺的是「driver 对这个形状不追加任何东西」,不是「服务器按某个顺序返回」。MySQL 给的是 InnoDB 主键序、PG 给的是堆序,把任何一个钉死都是把服务器的执行计划当成我们的契约来断言。三个格子共同断言的是「整集合原样返回」,而契约那一半(没有 ORDER BY 发出去)现在逐方言断言。
  3. emitted-SQL 那一半也参数化了,按方言的标识符引号(PG ",SQLite/MySQL `)构造断言 —— 于是「ORDER BY 真的到达了数据库」这句话在三个真引擎上分别成立,而不只是在 knex 的 SQLite 编译结果上。

诚实的边界:12 行只是一次 seq scan,真服务器也可能恰好稳定返回(本轮 MySQL 就是这样)。所以 emitted-SQL 那一半仍然是必要的,两半互相兜底 —— 头注里写清楚了。

约束遵守情况

  • ⛔ 只动 packages/plugins/driver-sql/**@objectstack/spec/data 只消费不修改 —— 没有为了变绿给 fixture 加 tiebreaker、改用例或放宽断言。
  • packages/spec/** 零改动;content/docs/releases/ 未碰;无新增 CI job(现有 job 已经在跑整包)。
  • 表名全部带 issue 前缀(os4714_pagination / os4714_pagination_clause / os4714_filter_logic / os4714_filter_logic_windows),替换掉原来裸的 t / task —— 共享 live 库上裸表名会和并行套件抢,抢输的那边会表现为一次假的 conformance 失败。

一处脚手架 bug(自己的,已修)

beforeEach(每条用例一个 :memory: driver)改成 beforeAll(live cell 承担不起每条断言一个连接)之后,nondeterministicPagingWarned 这个「每个 object 只警告一次」的账本开始跨用例共享,导致 says so, once, ... 数到 0 次。修法是给那条用例自己的 object 名,而不是去清账本 —— 属于 issue 里说的「红的是脚手架自身,那是你的 bug」。

关联

#4245 / PR #4713(driver 轴 + server-timezone 轴,temporal 矩阵)、#4081#4363、objectui#3106、#3774#4646;ADR-0053 D-A3。


🤖 Generated with Claude Code

https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny


Generated by Claude Code

claude added 2 commits August 3, 2026 01:04
… the driver axis (#4714)

#4245 (PR #4713) put the D-A3 driver axis under the temporal matrix and left a
reusable `live-dialect-matrix.testkit.ts` behind. Two other consumers of the
shared `@objectstack/spec/data` matrices in this package were still pinned to
one engine:

  - `sql-driver-pagination-conformance.test.ts` — `client: 'better-sqlite3'` in
    both describes, over `PAGINATION_CASES` / `PAGINATION_UNORDERED_CASES`;
  - `sql-driver-or-filter.test.ts` — same client, over `FILTER_LOGIC_CASES`,
    with the describe named `(SQLite)`.

The pagination one is the costly pin. Its own head note says the property half
proves nothing on SQLite: twelve rows come back in rowid order every time, so
the partition check passes with or without the tie-breaker. That is a local
fact about one engine, stated as a permanent excuse — on a real server the
property is the half with teeth, which is what objectui#3106 was reported as.

Both files now sweep once per cell of `DIALECT_CELLS` over the same cases,
asserting the same row-id sets cell for cell. Measured on a live PG 16 @
Asia/Shanghai and MariaDB 10.11 @ +08:00 under TZ=America/New_York: 46 tests per
cell, all three cells identical. With `paginationTieBreaker` sabotaged to return
null, the live-postgres cell reports 11 distinct ids over a 12-row walk — one
row served twice, another never — while every sorted case on SQLite stays green.
The cell exists and it bites.

- Table names carry the issue prefix (`os4714_*`); the bare `t` / `task` this
  suite used while SQLite-only would collide with a parallel suite on a shared
  live database and read as a conformance failure.
- `declareUnprovisionedCell` moves into the testkit and the temporal file now
  calls it too, so one definition of the non-vacuity guard serves all three
  matrices: missing URL is a named skip, and a red under
  `OS_EXPECT_LIVE_DIALECT_MATRIX=1` (#4646).
- No server-timezone axis here: nothing in these matrices compares an instant,
  so requiring a non-UTC server would only manufacture reds that say nothing
  about pagination or `$or`.
- The unpaged-unordered read keeps its exact-sequence pin on SQLite only. #4363
  promises the driver adds nothing to that shape, not that the server returns a
  particular order — MySQL hands back InnoDB primary-key order and Postgres its
  heap order, and pinning either would assert the server's plan as our contract.
  The contract half (no ORDER BY emitted) is asserted per dialect.
- No new CI job: `Temporal Conformance (live PG + MySQL)` already runs the whole
  package against both servers.

`@objectstack/spec/data` is consumed, not edited: no case was softened and no
tie-breaker was added to the fixture to keep a dialect green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 3, 2026 1:07am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-sql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-sql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 3, 2026 01:43
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit dcb1dad Aug 3, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4714-driver-axis-pagination-filter branch August 3, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

D-A3 的 driver 轴只补到了 temporal 矩阵:pagination / filter-logic 两条共享矩阵的 driver-sql 消费者仍钉死 SQLite

2 participants